package com.csjbot.snowbot_rogue.utils.map;

import com.csjbot.csjbase.log.Csjlogger;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class CsjMapUtil {

    private static CsjMapUtil util; // 单例对象
    public static final int GREEN_COLOR = 127; // 可行色
    public static final int EDGE_COLOR = -127; // 边缘色
    public static final int UNKNOW_COLOR = 0; // 未知色
    public static final int MIN_LENGTH = 50; // 返回地图的最小尺寸
    public static final int BLANK_LENGTH = 0; // 地图边缘留白大小
    public static final int DEFAULT_LEVEL = 27; // 默认颗粒度大小

    /**
     * 使用默认颗粒度去噪
     *
     * @param data
     * @param sideLength
     * @return 去噪后的地图
     */
    public CsjMap mapDenoise(int[] data, int sideLength) {
        return mapDenoise(data, sideLength, DEFAULT_LEVEL, false);
    }


    /**
     * 地图去噪方法 返回csj_map类型对象
     *
     * @param data
     * @param sideLength
     * @param lev        颗粒度大小
     * @return 去噪后的地图
     */
    public CsjMap mapDenoise(int[] data, int sideLength, int lev, boolean isDoCut) {
        CsjMap map = new CsjMap();
        map.setPreSideLength(sideLength);

        List<CsjPoint> points = getDenoisedPoints(data, sideLength, lev); // 获取可用点集
        int length = 0;
        int minX = sideLength, minY = sideLength, maxX = -1, maxY = -1;

        if (isDoCut) {
//        // 计算边界值
            for (CsjPoint point : points) {
                minX = min(minX, point.getX());
                maxX = max(maxX, point.getX());
                minY = min(minY, point.getY());
                maxY = max(maxY, point.getY());
            }

            length = max(maxX - minX, maxY - minY); // 计算边长
            length = max(length, MIN_LENGTH); // 边长不能小于最小长度
        } else {
            length = sideLength;
        }

        int[] mapData = new int[length * length]; // 默认全填0 即 UNKNOW_COLOR

        if (isDoCut) {
            // x 在原图里对应的范围
            for (int i = minX; i < minX + length; i++) {
                for (int j = minY; j < minY + length; j++) {
                    // mapData[(i-minX)*length + (j-minY)] 新图里面的坐标
                    if (data[i * sideLength + j] == 127) {
                        mapData[(i - minX) * length + (j - minY)] = data[i * sideLength + j];
                    }
                }
            }
        }

        for (CsjPoint point : points) { // 填入边界点
            try {
                mapData[(point.getX() + BLANK_LENGTH - minX) * length + (point.getY() + BLANK_LENGTH) - minY] = EDGE_COLOR;
            } catch (ArrayIndexOutOfBoundsException e) {
                Csjlogger.error(" point.getX() =  " + point.getX() + " point.getY()  = " + point.getY() + " sideLength = " + length +
                        " mapData = " + ((point.getX()) * length + (point.getY())));
            }
        }

        map.setData(mapData);
        map.setSideLength(length);
        if (isDoCut) {
            map.setUp_cutLength(minX);
            map.setRight_cutLength(sideLength - minX - length);
            map.setDown_cutLength(sideLength - minY - length);
            map.setLeft_cutLength(minY);
        }
        return map;
    }

    /**
     * 求两数较小值
     *
     * @param a
     * @param b
     * @return 两数较小值
     */
    private int min(int a, int b) {
        return a < b ? a : b;
    }

    /**
     * 求两数较大值
     *
     * @param a
     * @param b
     * @return 较大值
     */
    private int max(int a, int b) {
        return a > b ? a : b;
    }

    /**
     * 根据原始地图数据获取去噪后的边缘点集
     *
     * @param data
     * @param sideLength
     * @param lev
     * @return 去噪后的边缘点集
     */
    private List<CsjPoint> getDenoisedPoints(int[] data, int sideLength, int lev) {
        int[] mapData = data.clone(); // 调用clone方法 获取数据副本
        List<CsjPoint> points = new ArrayList<>(); // 边缘点收集器
        try {
            for (int i = 0, pos = 0; i < sideLength; ++i) {
                for (int j = 0; j < sideLength; ++j) {
                    if (isEdgePoint(mapData[pos++])) {
                        List<CsjPoint> linkedPoints = new LinkedList<>(); // 连接点集收集器
                        dfsEdgePoints(i, j, mapData, sideLength, linkedPoints, 0); // 深优搜索边缘点集
                        if (linkedPoints.size() <= lev) { // 判断为噪点集
                            // 去噪 即什么都不用做
                        } else {
                            points.addAll(linkedPoints); // 收集非噪点
                        }
                    }
                }
            }

        } catch (StackOverflowError error) {

        }

        return points;
    }

    /**
     * 深优收集边缘点 并将所经历过的点均归置为 UNKNOW_COLOR表示以访问过
     *
     * @param x
     * @param y
     * @param data
     * @param sideLength
     * @param list
     */
    private void dfsEdgePoints(int x, int y, int[] data, int sideLength, List<CsjPoint> list, int depth) {
//        if (depth > 100000) {
//            return;
//        }

        if (isEdgePoint(data[x * sideLength + y])) { // 当前为边缘点
            list.add(new CsjPoint(x, y)); // 加入点集
            data[x * sideLength + y] = UNKNOW_COLOR; // 标记已访问过
            depth++;

            if (x > 0) { // 可以向左访问
                dfsEdgePoints(x - 1, y, data, sideLength, list, depth);
            }
            if (x < (sideLength - 1)) { // 可以向右访问
                dfsEdgePoints(x + 1, y, data, sideLength, list, depth);
            }
            if (y > 0) { // 可以向上访问
                dfsEdgePoints(x, y - 1, data, sideLength, list, depth);
            }
            if (y < (sideLength - 1)) { // 可以向下访问
                dfsEdgePoints(x, y + 1, data, sideLength, list, depth);
            }
        }
        // 当前不是边缘点 则什么都不用做
    }

    /**
     * 根据颜色值判断是否为边缘点
     *
     * @param color
     * @return
     */
    private boolean isEdgePoint(int color) {
        return color < UNKNOW_COLOR;
    }

    /**
     * 获取实例方法
     *
     * @return 单例对象
     */
    public static CsjMapUtil getInstance() {
        if (util == null) {
            util = new CsjMapUtil();
        }
        return util;
    }

    /**
     * 私有化构造方法以支持单例
     */
    private CsjMapUtil() {
        // 私有化构造方法
    }

}
